package lifeOfAFish.scenegraph;
import java.util.*;
import javax.media.opengl.*;

/**
 * This is a class used by the scenegraph for correctly blending and rendering 
 * translucent geometries. 
 * @author Erik Bonner
 *
 */
public class sgGeomBlender {
	
	// private class for wrapping and sorting
	// geometry objects, according to their distance to the camera
	private class BlendObject implements Comparable<BlendObject> {
		private sgGeometry m_geom;
		
		public BlendObject(sgGeometry geom)
		{
			m_geom = geom;
		}
		
		/**
		 * Compares two objects to see which is most far from the eye. 
		 */
		public int compareTo(BlendObject o){
			
			// get bounding boxes of the two objects
			sgVec3[] verts = m_geom.getGlobalBoundingBox();
			sgVec3 eye = sgGeomBlender.getInstance().getCam().m_eye;
			
			// get min distance from bounding box vertex to the eye
			float minDist0 = 1000000;
			for (int i = 0; i < 8; i++ )
			{
				float d = sgVec3.subtract(eye, verts[i]).length();
				if (d < minDist0) minDist0 = d;
			}
			
			// get min distance from bounding box vertex to the eye
			verts = o.m_geom.getGlobalBoundingBox();
			float minDist1 = 1000000;
			for (int i = 0; i < 8; i++ )
			{
				float d = sgVec3.subtract(eye, verts[i]).length();
				if (d < minDist1) minDist1 = d;
			}
			
			// now compare the min distances
			if (minDist0 > minDist1) return 1;
			else if (minDist0 < minDist1) return -1;
			else return 0;
		}
	}
	
	private sgCamera m_cam = new sgCamera();
	private Vector<BlendObject> m_list = new Vector<BlendObject>();
	private static sgGeomBlender instance = null;
	private sgMaterial mat; 
	private sgMaterial mat2; 
	private boolean m_debug = false;
	
	/**
	 * We using the Singleton design pattern here. 
	 * @return the singleton instance
	 */
	public static sgGeomBlender getInstance() {
		if (instance == null) instance = new sgGeomBlender();
		return instance;
	}
	
	/**
	 * Constructor
	 */
	public sgGeomBlender()
	{
		mat = new sgMaterial();
		mat.setAmbient(0.0f, 0.0f, 1.0f, 0.5f);
		mat.setDiffuse(0.0f, 0.0f, 1.0f, 0.5f);
		mat.setSpecular(0.0f, 0.0f, 1.0f, 0.5f);
		mat.setEmission(0.0f, 0.0f, 1.0f, 0.5f);
		
		mat2 = new sgMaterial();
		mat2.setAmbient(1.0f, 0.0f, 1.0f, 1.0f);
		mat2.setDiffuse(1.0f, 0.0f, 1.0f, 1.0f);
		mat2.setSpecular(1.0f, 0.0f, 1.0f, 1.0f);
		mat2.setEmission(1.0f, 0.0f, 1.0f, 1.0f);
		
	}
	
	public void setDebug(boolean dbg){
		m_debug = dbg;
	}
	public boolean getDebug(){
		return m_debug;
	}
	
	/**
	 * Set the camera that is viewing the scene
	 */
	public void setCam(sgCamera cam){
		m_cam = cam;
	}
	
	/**
	 * Get the camera that is viewing the scene
	 */
	public sgCamera getCam(){
		return m_cam;
	}
	
	
	/**
	 * add another object to the list of objects to be rendered.
	 */
	public void addGeom(sgGeometry geom)
	{
		if(!geom.inBlender())
		{
			BlendObject bo = new BlendObject(geom);
			m_list.add(bo);
			
			geom.addToBlender();
		}
		
	}
	
	
	/**
	 * render all of the transparent objects in the scene
	 */
	public void render()
	{
		GL gl = sgGlobals.gl;
		
		Collections.sort(m_list);
		
		if(m_debug && m_list.size() > 0)
		{
			m_list.get(0).m_geom.setMaterial(mat);
			for (int c = 1; c < m_list.size(); c++)
				m_list.get(c).m_geom.setMaterial(mat2);
		}
	
		// disable backface culling and enbale blending
		gl.glEnable(GL.GL_BLEND);
		
		// enable alpha blending
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		
		// disable depth masking 
		gl.glDepthMask(false);
		
		// render the objects from back to front
		for (int c = m_list.size()-1; c >= 0; c--)
		{
			gl.glPushMatrix();
			m_list.get(c).m_geom.push();
			gl.glLoadIdentity();
			gl.glLoadMatrixf(m_list.get(c).m_geom.getModelView(), 0);
			m_list.get(c).m_geom.setForce(true);
			m_list.get(c).m_geom.render();
			m_list.get(c).m_geom.setForce(false);
			m_list.get(c).m_geom.pop();
			gl.glPopMatrix();
		}
		
		// turn everything back on
		gl.glDisable(GL.GL_BLEND);
		gl.glDepthMask(true);
	}
	
	/**
	 * Print the contents of the list
	 */
	public String toString(){
		String str = "";
		for(int i = 0; i < m_list.size(); i++)
		{
			str += m_list.get(i).m_geom.getName() + "\n";
		}
		return str;
	}
	
	
}
